﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using LayeredArchitectureSample.DataTransferObjects;
using LayeredArchitectureSample.DomainModel;
using LayeredArchitectureSample.Infrastructure;
using LayeredArchitectureSample.Infrastructure.Services;
using LayeredArchitectureSample.RandomDataProviders;
using LayeredArchitectureSample.ServiceContracts;

namespace LayeredArchitectureSample.ViewModel
{
    internal class TripsViewModel : INotifyPropertyChanged
    {
        private readonly IServiceOperationInvoker<ITripManager> _tripManager;

        private readonly ObservableCollection<TripViewModel> _trips;
        private TripViewModel _currentlySelectedTrip;
        private bool _isSomethingSelected;
        private readonly string _somethingSelectedPropertyName;
        private readonly string _selectedTripPropertyName;
        private readonly IRandomRouteProvider _randomRouteProvider;
        private readonly IRandomWareProvider _randomWareProvider;
        private bool _canShipSelectedTrip;
        private readonly string _canShipPropertyName;
        private bool _canMarkSelectedTripAsArrived;
        private readonly string _canMarkSelectedTripAsArrivedPropertyName;
        private readonly IExceptionAwareServiceCodeRunner _codeRunner;

        public TripsViewModel(
            IEnumerable<TripViewModel> trips,
            IServiceOperationInvoker<ITripManager> tripManager,
            IRandomRouteProvider randomRouteProvider,
            IRandomWareProvider randomWareProvider,
            IExceptionAwareServiceCodeRunner codeRunner)
        {
            if (trips == null) throw new ArgumentNullException("trips");
            if (tripManager == null) throw new ArgumentNullException("tripManager");
            if (randomRouteProvider == null) throw new ArgumentNullException("randomRouteProvider");
            if (randomWareProvider == null) throw new ArgumentNullException("randomWareProvider");
            if (codeRunner == null) throw new ArgumentNullException("codeRunner");

            _trips = new ObservableCollection<TripViewModel>(trips);

            _somethingSelectedPropertyName = Reflection.GetPropertyName(() => this.IsSomethingSelected);
            _selectedTripPropertyName = Reflection.GetPropertyName(() => this.CurrentlySelectedTrip);
            _canShipPropertyName = Reflection.GetPropertyName(() => this.CanShipSelectedTrip);
            _canMarkSelectedTripAsArrivedPropertyName = Reflection.GetPropertyName(() => this.CanMarkSelectedTripAsArrived);

            _tripManager = tripManager;
            _codeRunner = codeRunner;
            _randomWareProvider = randomWareProvider;
            _randomRouteProvider = randomRouteProvider;
        }

        public ObservableCollection<TripViewModel> Trips
        {
            get { return _trips; }
        }

        public TripViewModel CurrentlySelectedTrip
        {
            get { return _currentlySelectedTrip; }
            set
            {
                _currentlySelectedTrip = value;

                this.IsSomethingSelected = (_currentlySelectedTrip != null);
                EventRaiser.RaisePropertyChangedEvent(PropertyChanged, this, _selectedTripPropertyName);

                CanMarkSelectedTripAsArrived = SeeIfWeCanMarkSelectedTripAsArrived();
            }
        }

        private bool SeeIfWeCanMarkSelectedTripAsArrived()
        {
            return _currentlySelectedTrip != null &&
                   _currentlySelectedTrip.State == TripState.EnRoute;
        }

        public bool IsSomethingSelected
        {
            get
            {
                return _isSomethingSelected;
            }
            set
            {
                _isSomethingSelected = value;
                EventRaiser.RaisePropertyChangedEvent(PropertyChanged, this, _somethingSelectedPropertyName);
            }
        }

        public bool CanShipSelectedTrip
        {
            get
            {
                return _canShipSelectedTrip;
            }
            set
            {
                _canShipSelectedTrip = value;
                EventRaiser.RaisePropertyChangedEvent(PropertyChanged, this, _canShipPropertyName);
            }
        }

        public bool CanMarkSelectedTripAsArrived
        {
            get
            {
                return _canMarkSelectedTripAsArrived;
            }
            set
            {
                _canMarkSelectedTripAsArrived = value;
                EventRaiser.RaisePropertyChangedEvent(PropertyChanged, this, _canMarkSelectedTripAsArrivedPropertyName);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void MarkSelectedTripAsArrived()
        {
            _codeRunner.RunCode(
                () =>
                    {
                        int tripBeforeUpdateIndex = _trips.IndexOf(CurrentlySelectedTrip);

                        TripDto updatedTrip = _tripManager.Invoke(t => t.MarkAsArrived(CurrentlySelectedTrip.Id));

                        _trips[tripBeforeUpdateIndex] = GetTripViewModelFromDto(updatedTrip);
                    });
        }

        public void CreateTripAndSendItToRandomLocation()
        {
            _codeRunner.RunCode(() =>
            {
                RouteDto route = _randomRouteProvider.GetRandomRoute();

                IEnumerable<WareUnitsCountedDto> wares = _randomWareProvider.GetRandomWares(2);

                TripDto newTrip = _tripManager.Invoke(t => t.CreateShippedTrip(route, wares));

                TripViewModel tripViewModel = GetTripViewModelFromDto(newTrip);

                _trips.Add(tripViewModel);
            });
        }

        private static TripViewModel GetTripViewModelFromDto(TripDto newTrip)
        {
            List<WareInTripViewModel> wares = newTrip.Wares.Select(w => new WareInTripViewModel(new WareViewModel(w.Ware.Id, w.Ware.Code), w.Quantity)).ToList();
            var route = new RouteViewModel(newTrip.Route.Waypoints.Select(w => new LocationViewModel(w.Id, w.Name)), newTrip.Route.Id);

            return new TripViewModel(wares, newTrip.Id, route, newTrip.Status, (TripState)Enum.Parse(typeof(TripState), newTrip.State));
        }
    }
}